home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ian & Stuart's Australian Mac 1993 September
/
September 93.iso
/
Archives
/
Sound
/
MIDI
/
MIDI Utilities
/
CMU Midi Toolkit
/
Source
/
midiparse.c
< prev
next >
Wrap
Text File
|
1987-01-27
|
12KB
|
387 lines
/* midiparse.c
*
* midiparse parses incoming midi data. the data arrives one byte at a
* time via calls to process_byte from the input interrupt handler.
* normal (NOT system exclusive) midi messages are placed in the shared
* buffer "buff" (defined in macmidi.c) while system exclusive messages
* are placed in "xbuff", if it is not NULL. (xbuff is supplied by the
* client, if he is interested in system exclusive messages, through a
* call to midi_buffer.) if the shared variable "ctrlFilter" is true,
* then continuous controller data and key/channel after-touch are
* filtered out of the midi stream. "ctrlFilter" may be set by calling
* midi_cont in macmidi.c.
*/
/*****************************************************************************
* Change Log
* Date | Change
*-----------+-----------------------------------------------------------------
* 16-Jan-87 | JMaloney: Converted to Lightspeed C
*****************************************************************************/
#include <StdIO.h>
#include "cext.h"
#include "midibuff.h"
#include "midiparse.h"
/****************************************************************************
*
* constants and types
*
****************************************************************************/
/* some constants for parsing MIDI status bytes */
#define STATUS_BIT 0x80
#define HIGH_NIBBLE 0xF0
#define LOW_NIBBLE 0x0F
#define SYSTEM_MESSAGE 0xF0
#define CHANNEL_CMD 0x70
/* some MIDI status bytes */
#define END_BLOCK_STATUS 0xF7
#define NOTE_OFF_STATUS 0x80
#define PITCH_BEND_STATUS 0xE0
enum receive_state {
idle, system_exclusive, interesting, boring};
/****************************************************************************
*
* variables shared with other modules
*
****************************************************************************/
/* imported: midi input buffer */
extern byte buff[BUFF_SIZE]; /* data buffer */
extern int bufftail; /* buffer tail (insertion point) */
extern int buffhead; /* buffer head */
/* imported: user supplied system exclusive buffer */
extern byte *xbuff; /* address of the user-supplied buffer (or NULL) */
extern int xbuffmask; /* mask for circular buffer address calculation */
extern int xbufftail; /* buffer tail (insertion point) */
extern int xbuffhead; /* buffer head */
/* imported: */
extern boolean ctrlFilter;
/* exported: errors are signalled by setting bits in midi_error */
public int midi_error = 0;
/****************************************************************************
*
* variables private to this module (parser's "state")
*
****************************************************************************/
private int last_status_byte = 0; /* status byte of the current
* or most recent command; keeps midi
* "running status" */
private int bytes_wanted = 0; /* bytes of data for current command */
private byte data[2]; /* buffer in which to accumulate data bytes */
private int rcv_count = 0; /* number of bytes in "data" */
private enum receive_state state, saved_state = idle;
/* state is the current state
* saved_state is the state pushed when we are interrupted by
* a higher priority (i.e. system exclusive) message */
/****************************************************************************
*
* routines private to this module
*
****************************************************************************/
void block_end(void);
void data_byte(int);
void put_xbyte(int);
void reset(void);
void set_error(int);
void state_error(void);
void undefined(void);
void unexpected(void);
void use_running_status(int);
/****************************************************************************
* block_end
* Effect:
* a block end status byte terminates a system exclusive message
****************************************************************************/
private void block_end()
{
/* put eob in system exclusive buffer */
put_xbyte(END_BLOCK_STATUS);
state = saved_state; /* restore state to what it was when we were
saved_state = idle; * so rudely interrupted */
}
/****************************************************************************
* data_byte
* Effect:
* accept a data byte.
* the behavior of this procedure depends on the state of the parser.
* if we are in the midst of receiving a sytem exclusive message:
* put the data byte in the system exclusive buffer
* if this byte completes a midi command:
* if it is an "interesting" command,
* put the entire command into the normal midi buffer
* in any event, reset the parser to the idle state
* if we are in the idle state:
* use "running status" to figure what the command byte for this
* data byte should have been and set the parser state accordingly
****************************************************************************/
private void data_byte(c)
register int c;
{
if (state == system_exclusive) {
/* midi exclusive data byte */
if (xbuff != NULL) put_xbyte(c);
} else if (bytes_wanted > 0) {
/* data byte for a midi command */
data[rcv_count++] = (byte) c;
if (rcv_count == bytes_wanted) { /* got a complete command */
if (state == interesting) { /* interesting, so save it */
register byte *dataPtr;
dataPtr = buff + bufftail;
*dataPtr++ = last_status_byte;
*dataPtr++ = data[0];
*dataPtr++ = data[1];
*dataPtr++ = 0; /* there are at most two data bytes */
bufftail = (bufftail + 4) & BUFF_MASK;
if (bufftail == buffhead) {
/* filled buffer faster than client emptied it */
set_error(DATA_OVERRUN);
}
}
reset();
}
} else {
/* must be in the idle state; this is a data byte with an implied
* status byte equal to that of the last status byte received */
use_running_status(c);
}
}
/****************************************************************************
* process_byte
* Effect:
* the heart of the parser.
* see the midi 1.0 specification for clarification.
****************************************************************************/
public void process_byte(c)
register int c;
{
if (!(c & STATUS_BIT)) {
/* data byte for a command */
data_byte(c);
} else {
/* midi status byte -- the start of a command */
if ((c & HIGH_NIBBLE) == SYSTEM_MESSAGE) {
/* a system message */
switch (c & LOW_NIBBLE) {
/* case 0 is a system exclusive message */
case 0:
if (saved_state != idle) state_error();
/* a system exclusive message cannot interrupt
* another system exclusive message! */
saved_state = state;
state = system_exclusive;
if (xbuff != NULL) put_xbyte(c);
break;
/* cases 1 through 7 are system common messages */
/* case 1: *** undefined -- see default case *** */
case 2: /* measure select (msbits, lsbits) */
if (state != idle) state_error();
last_status_byte = c;
state = boring;
bytes_wanted = 2;
unexpected();
break;
case 3: /* song select (song_number) */
if (state != idle) state_error();
last_status_byte = c;
state = boring;
bytes_wanted = 1;
unexpected();
break;
/* case 4: *** undefined -- see default case *** */
/* case 5: *** undefined -- see default case *** */
case 6: /* tune request */
unexpected();
break;
case 7: /* end of block */
block_end();
break;
/* cases 8 through 15 are system real-time messages */
/* they have no data bytes and are not of interest to us */
case 8: /* timing clock - during play */
case 9: /* timing clock with measure end */
case 10: /* start - from first measure */
case 11: /* start - continue */
case 12: /* timing clock - during stop */
unexpected();
break;
/* case 13: *** undefined -- see default case *** */
case 14: /* running status */
/* expected but boring -- throw it away */;
break;
case 15: /* system reset */
unexpected();
break;
default:
undefined();
break;
}
} else {
/* channel message */
switch ((c & CHANNEL_CMD) >> 4) {
case 0: /* note off */
if (state != idle) state_error();
last_status_byte = c;
state = interesting;
bytes_wanted = 2;
break;
case 1: /* note on */
if (state != idle) state_error();
last_status_byte = c;
state = interesting;
bytes_wanted = 2;
break;
case 2: /* key pressure */
if (state != idle) state_error();
last_status_byte = c;
state = (ctrlFilter) ? boring : interesting;
bytes_wanted = 2;
break;
case 3: /* control change */
if (state != idle) state_error();
last_status_byte = c;
state = (ctrlFilter) ? boring : interesting;
bytes_wanted = 2;
break;
case 4: /* program change */
if (state != idle) state_error();
last_status_byte = c;
state = interesting;
bytes_wanted = 1;
break;
case 5: /* channel pressure */
if (state != idle) state_error();
last_status_byte = c;
state = (ctrlFilter) ? boring : interesting;
bytes_wanted = 1;
break;
case 6: /* pitch bend */
if (state != idle) state_error();
last_status_byte = c;
state = (ctrlFilter) ? boring : interesting;
bytes_wanted = 2;
break;
case 7: /* system message, handled above */
default:
undefined();
break;
}
}
}
}
/****************************************************************************
* put_xbyte
* Effect:
* puts a midi-exclusive message byte into the client supplied buffer, if
* there is one. notices overrun errors.
****************************************************************************/
private void put_xbyte(c)
register int c;
{
if (xbuff != NULL) {
/* if the client has supplied a buffer, */
/* save system exclusive data in it */
xbuff[xbufftail++] = (byte) c;
xbufftail &= xbuffmask;
if (xbufftail == xbuffhead) {
/* filled buffer faster than client emptied it */
set_error(EXCLUSIVE_DATA_OVERRUN);
}
}
}
/****************************************************************************
* reset
* Effect:
* resets the parser's state after a complete midi command is received or
* after a state error occurs
****************************************************************************/
private void reset()
{
state = idle;
saved_state = idle;
bytes_wanted = 0;
rcv_count = 0;
}
/****************************************************************************
* error handling
* Effect:
* various error conditions are flagged by setting bits in
* the global midi_error. it is up to the client to clear this word
* when necessary.
****************************************************************************/
private void set_error(bit)
int bit;
{
midi_error |= bit;
}
private void state_error()
{
midi_error |= STATE_ERR;
reset();
}
private void undefined()
{
midi_error |= UNDEFINED_OP;
}
private void unexpected()
{
midi_error |= UNEXPECTED_OP;
}
/****************************************************************************
* use_running_status
* Effect:
* called when a data byte arrives out of the blue, meaning that
* we should use the midi "running status" feature -- the command
* byte is implied to be the same as the last command byte received
* Implementation:
* recursively calles process_byte to establish the right context
****************************************************************************/
private void use_running_status(data_byte)
int data_byte;
{
/* Note: only channel messages may use running status */
if ((state == idle) &&
(((last_status_byte >= NOTE_OFF_STATUS) &&
(last_status_byte <= PITCH_BEND_STATUS)))) {
process_byte(last_status_byte); /* recursive call */
process_byte(data_byte); /* recursive call */
} else {
set_error(UNWANTED_DATA);
}
}